#include "linux_platform.h"
#ifdef __linux__
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <stdexcept>
#include <string>

#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/spi/spidev.h> // Needed for SPI port
#include <net/if.h>
#include <net/if_arp.h>
#include <netdb.h>
#include <netinet/in.h>
#include <poll.h> // Needed for GPIO edge detection
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/ioctl.h> // Needed for SPI port
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h> // Needed for delayMicroseconds()
#include <sys/types.h>
#include <termios.h>
#include <unistd.h>

#include "knx/address_table_object.h"
#include "knx/application_program_object.h"
#include "knx/association_table_object.h"
#include "knx/bits.h"
#include "knx/device_object.h"
#include "knx/group_object_table_object.h"
#include "knx/ip_host_protocol_address_information.h"
#include "knx/ip_parameter_object.h"

#define MAX_MEM 4096

LinuxPlatform::LinuxPlatform() {
  int socketMac = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
  if (socketMac < 0) {
    printf("Lookup socket creation failed");
    return;
  }

  struct ifreq ifr;

  struct ifconf ifc;

  char buf[1024];

  ifc.ifc_len = sizeof(buf);

  ifc.ifc_buf = buf;

  if (ioctl(socketMac, SIOCGIFCONF, &ifc) < 0)
    return;

  struct ifreq *it = ifc.ifc_req;
  const struct ifreq *const end = it + (ifc.ifc_len / sizeof(struct ifreq));

  for (; it != end; ++it) {
    strcpy(ifr.ifr_name, it->ifr_name);

    if (ioctl(socketMac, SIOCGIFFLAGS, &ifr))
      continue;

    if (ifr.ifr_flags & IFF_LOOPBACK) // don't count loopback
      continue;

    if (ioctl(socketMac, SIOCGIFHWADDR, &ifr))
      continue;

    if (ifr.ifr_hwaddr.sa_family != ARPHRD_ETHER)
      continue;

    memcpy(_macAddress, ifr.ifr_hwaddr.sa_data, IFHWADDRLEN);

    ioctl(socketMac, SIOCGIFADDR, &ifr);

    struct sockaddr_in *ipaddr = (struct sockaddr_in *)&ifr.ifr_addr;
    _ipAddress = ntohl(ipaddr->sin_addr.s_addr);

    printf("IP address: %s\n", inet_ntoa(ipaddr->sin_addr));
    ioctl(socketMac, SIOCGIFNETMASK, &ifr);
    struct sockaddr_in *netmask = (struct sockaddr_in *)&ifr.ifr_netmask;
    _netmask = ntohl(netmask->sin_addr.s_addr);
    printf("Netmask: %s\n", inet_ntoa(ipaddr->sin_addr));
    break;
  }

  close(socketMac);

  // default GW
  FILE *f;
  char line[100], *p, *c, *g, *saveptr;

  f = fopen("/proc/net/route", "r");

  while (fgets(line, 100, f)) {
    p = strtok_r(line, " \t", &saveptr);
    c = strtok_r(NULL, " \t", &saveptr);
    g = strtok_r(NULL, " \t", &saveptr);

    if (p != NULL && c != NULL) {
      if (strcmp(c, "00000000") == 0) {
        printf("Default interface is : %s \n", p);
        if (g) {
          char *pEnd;
          _defaultGateway = ntohl(strtol(g, &pEnd, 16));
        }

        break;
      }
    }
  }

  fclose(f);
}

LinuxPlatform::~LinuxPlatform() { delete[] _args; }

uint32_t millis() {
  struct timespec spec;

  clock_gettime(CLOCK_MONOTONIC, &spec);
  return spec.tv_sec * 1000 + round(spec.tv_nsec / 1.0e6);
}

void delay(uint32_t millis) {
  struct timespec ts;
  ts.tv_sec = millis / 1000;
  ts.tv_nsec = (millis % 1000) * 1000000;
  nanosleep(&ts, NULL);
}

void LinuxPlatform::restart() { execv(_args[0], _args); }

void LinuxPlatform::fatalError() {
  printf("A fatal error occured. Stopping.\n");

  while (true)
    sleep(1);
}

void LinuxPlatform::setupMultiCast(uint32_t addr, uint16_t port) {
  if (_multicastSocketFd >= 0)
    closeMultiCast();

  _multicastAddr = addr;
  _multicastPort = port;

  struct ip_mreq command;
  uint32_t loop = 1;

  struct sockaddr_in sin;
  memset(&sin, 0, sizeof(sin));
  sin.sin_family = AF_INET;
  sin.sin_addr.s_addr = htonl(INADDR_ANY);
  sin.sin_port = htons(port);

  _multicastSocketFd = socket(AF_INET, SOCK_DGRAM, 0);

  if (_multicastSocketFd == -1) {
    perror("socket()");
    fatalError();
  }

  /* Mehr Prozessen erlauben, denselben Port zu nutzen */
  loop = 1;

  if (setsockopt(_multicastSocketFd, SOL_SOCKET, SO_REUSEADDR, &loop,
                 sizeof(loop)) < 0) {
    perror("setsockopt:SO_REUSEADDR");
    fatalError();
  }

  if (bind(_multicastSocketFd, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
    perror("bind");
    fatalError();
  }

  /* loopback */
  loop = 0;

  if (setsockopt(_multicastSocketFd, IPPROTO_IP, IP_MULTICAST_LOOP, &loop,
                 sizeof(loop)) < 0) {
    perror("setsockopt:IP_MULTICAST_LOOP");
    fatalError();
  }

  /* Join the broadcast group: */
  command.imr_multiaddr.s_addr = htonl(addr);
  command.imr_interface.s_addr = htonl(INADDR_ANY);

  if (setsockopt(_multicastSocketFd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &command,
                 sizeof(command)) < 0) {
    perror("setsockopt:IP_ADD_MEMBERSHIP");
    fatalError();
  }

  uint32_t flags = fcntl(_multicastSocketFd, F_GETFL);
  flags |= O_NONBLOCK;
  fcntl(_multicastSocketFd, F_SETFL, flags);
}

void LinuxPlatform::closeMultiCast() {
  struct ip_mreq command;
  command.imr_multiaddr.s_addr = htonl(_multicastAddr);
  command.imr_interface.s_addr = htonl(INADDR_ANY);

  if (setsockopt(_multicastSocketFd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &command,
                 sizeof(command)) < 0) {
    perror("setsockopt:IP_DROP_MEMBERSHIP");
  }

  close(_multicastSocketFd);
  _multicastSocketFd = -1;
}

bool LinuxPlatform::sendBytesMultiCast(uint8_t *buffer, uint16_t len) {
  struct sockaddr_in address = {0};
  address.sin_family = AF_INET;
  address.sin_addr.s_addr = htonl(_multicastAddr);
  address.sin_port = htons(_multicastPort);

  ssize_t retVal = 0;

  do {
    retVal = sendto(_multicastSocketFd, buffer, len, 0,
                    (struct sockaddr *)&address, sizeof(address));

    if (retVal == -1) {
      if (errno != EAGAIN && errno != EWOULDBLOCK)
        return false;
    }
  } while (retVal == -1);

  printHex("<-", buffer, len);
  return true;
}

int LinuxPlatform::readBytesMultiCast(uint8_t *buffer, uint16_t maxLen) {
  uint32_t sin_len;
  struct sockaddr_in sin;

  sin_len = sizeof(sin);
  ssize_t len = recvfrom(_multicastSocketFd, buffer, maxLen, 0,
                         (struct sockaddr *)&sin, &sin_len);
  if (len > 0)
    printHex("->", buffer, len);

  return len;
}

uint8_t *LinuxPlatform::getEepromBuffer(uint32_t size) {
  if (_fd < 0)
    doMemoryMapping();

  return _mappedFile + 2;
}

void LinuxPlatform::commitToEeprom() {
  if (_fd < 0)
    doMemoryMapping();

  fsync(_fd);
}

#define FLASHSIZE 0x10000
void LinuxPlatform::doMemoryMapping() {
  _fd = open(_flashFilePath.c_str(), O_RDWR | O_CREAT,
             S_IRWXU | S_IRGRP | S_IROTH);

  if (_fd < 0) {
    puts("Error in file opening");
    // exit(-1);
  }

  struct stat st;

  uint32_t ret = fstat(_fd, &st);

  if (ret < 0) {
    puts("Error in fstat");
    // exit(-1);
  }

  size_t len_file = st.st_size;

  if (len_file < FLASHSIZE) {
    if (ftruncate(_fd, FLASHSIZE) != 0) {
      puts("Error extending file");
      // exit(-1);
    }

    len_file = FLASHSIZE;
  }

  unsigned char *addr = (unsigned char *)mmap(
      NULL, len_file, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, 0);

  if (addr[0] != 0xAF || addr[1] != 0xFE) {
    memset(addr, 0, FLASHSIZE);
    addr[0] = 0xAF;
    addr[1] = 0xFE;
  }

  if (addr == MAP_FAILED) {
    puts("Error in mmap");
    // exit(-1);
  }

  _mappedFile = addr;
}

void LinuxPlatform::closeSpi() {
  close(_spiFd);
  printf("SPI device closed.\r\n");
}

int LinuxPlatform::readWriteSpi(uint8_t *data, size_t len) {
  uint16_t spiDelay = 0;
  uint32_t spiSpeed = 8000000; // 4 MHz SPI speed
  uint8_t spiBPW = 8;          // Bits per word

  struct spi_ioc_transfer spi;

  // Mentioned in spidev.h but not used in the original kernel documentation
  //  test program )-:

  memset(&spi, 0, sizeof(spi));

  spi.tx_buf = (uint64_t)data;
  spi.rx_buf = (uint64_t)data;
  spi.len = len;
  spi.delay_usecs = spiDelay;
  spi.speed_hz = spiSpeed;
  spi.bits_per_word = spiBPW;

  return ioctl(_spiFd, SPI_IOC_MESSAGE(1), &spi);
}

void LinuxPlatform::setupSpi() {
  if ((_spiFd = open("/dev/spidev0.0", O_RDWR)) < 0) {
    printf("ERROR: SPI setup failed! Could not open SPI device!\r\n");
    return;
  }

  // Set SPI parameters.
  int mode = 0;        // Mode 0
  uint8_t spiBPW = 8;  // Bits per word
  int speed = 8000000; // 4 MHz SPI speed

  if (ioctl(_spiFd, SPI_IOC_WR_MODE, &mode) < 0) {
    printf("ERROR: SPI Mode Change failure: %s\n", strerror(errno));
    close(_spiFd);
    return;
  }

  if (ioctl(_spiFd, SPI_IOC_WR_BITS_PER_WORD, &spiBPW) < 0) {
    printf("ERROR: SPI BPW Change failure: %s\n", strerror(errno));
    close(_spiFd);
    return;
  }

  if (ioctl(_spiFd, SPI_IOC_WR_MAX_SPEED_HZ, &speed) < 0) {
    printf("ERROR: SPI Speed Change failure: %s\n", strerror(errno));
    close(_spiFd);
    return;
  }

  printf("SPI device setup ok.\r\n");
}

void LinuxPlatform::flashFilePath(const std::string path) {
  _flashFilePath = path;
}

std::string LinuxPlatform::flashFilePath() { return _flashFilePath; }

size_t LinuxPlatform::readBytesUart(uint8_t *buffer, size_t length) {
  return read(_uartFd, buffer, length);
}

int LinuxPlatform::readUart() {
  uint8_t x;

  if (read(_uartFd, &x, 1) != 1) {
    return -1;
  }

  return ((int)x) & 0xFF;
}

size_t LinuxPlatform::writeUart(const uint8_t *buffer, size_t size) {
  return write(_uartFd, buffer, size);
}

size_t LinuxPlatform::writeUart(const uint8_t data) {
  return write(_uartFd, &data, 1);
}

int LinuxPlatform::uartAvailable() {
  int result;

  if (ioctl(_uartFd, FIONREAD, &result) == -1) {
    return -1;
  }

  return result;
}

void LinuxPlatform::closeUart() {
  if (_uartFd >= 0) {
    close(_uartFd);
  }
}

void LinuxPlatform::setupUart() {
  /*
   * 19200,8E1, no handshake
   */
  struct termios options; /* Schnittstellenoptionen */

  /* Port oeffnen - read/write, kein "controlling tty", Status von DCD
   * ignorieren */
  _uartFd = open(_args[1], O_RDWR | O_NOCTTY | O_NDELAY);

  if (_uartFd >= 0) {
    /* get the current options */
    fcntl(_uartFd, F_SETFL, 0);

    if (tcgetattr(_uartFd, &options) != 0) {
      close(_uartFd);
      _uartFd = -1;
      return;
    }

    memset(&options, 0,
           sizeof(options)); /* Structur loeschen, ggf. vorher sichern
                            und bei Programmende wieder restaurieren */
    /* Baudrate setzen */
    cfsetispeed(&options, B19200);
    cfsetospeed(&options, B19200);

    /* setze Optionen */
    options.c_cflag |= PARENB;  /* Enable Paritybit */
    options.c_cflag &= ~PARODD; /* Even parity */
    options.c_cflag &= ~CSTOPB; /* 1 Stoppbit */
    options.c_cflag &= ~CSIZE;  /* 8 Datenbits */
    options.c_cflag |= CS8;

    /* 19200 bps, 8 Datenbits, CD-Signal ignorieren, Lesen erlauben */
    options.c_cflag |= (CLOCAL | CREAD);

    /* Kein Echo, keine Steuerzeichen, keine Interrupts */
    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    options.c_iflag = IGNPAR;    /* Parity-Fehler ignorieren */
    options.c_oflag &= ~OPOST;   /* setze "raw" Input */
    options.c_cc[VMIN] = 0;      /* warten auf min. 0 Zeichen */
    options.c_cc[VTIME] = 10;    /* Timeout 1 Sekunde */
    tcflush(_uartFd, TCIOFLUSH); /* Puffer leeren */

    if (tcsetattr(_uartFd, TCSAFLUSH, &options) != 0) {
      close(_uartFd);
      _uartFd = -1;
      return;
    }
  } else {
    _uartFd = -1;
    printf("UART: Can't open %s\n", _args[1]);
    exit(0);
    return;
  }
}

#ifndef KNX_NO_PRINT
void printUint64(uint64_t value, int base = DEC) {
  char buf[8 * sizeof(uint64_t) + 1];
  char *str = &buf[sizeof(buf) - 1];
  *str = '\0';

  uint64_t n = value;

  do {
    char c = n % base;
    n /= base;

    *--str = c < 10 ? c + '0' : c + 'A' - 10;
  } while (n > 0);

  print(str);
}

void print(const char *s) { printf("%s", s); }
void print(char c) { printf("%c", c); }

void print(unsigned char num) { print(num, DEC); }

void print(unsigned char num, int base) {
  if (base == HEX)
    printf("%X", num);
  else
    printf("%d", num);
}

void print(int num) { print(num, DEC); }

void print(int num, int base) {
  if (base == HEX)
    printf("%X", num);
  else
    printf("%d", num);
}

void print(unsigned int num) { print(num, DEC); }

void print(unsigned int num, int base) {
  if (base == HEX)
    printf("%X", num);
  else
    printf("%d", num);
}

void print(long num) { print(num, DEC); }

void print(long num, int base) {
  if (base == HEX)
    printf("%lX", num);
  else
    printf("%ld", num);
}

void print(unsigned long num) { print(num, DEC); }

void print(unsigned long num, int base) {
  if (base == HEX)
    printf("%lX", num);
  else
    printf("%ld", num);
}

void print(unsigned long long num) { printUint64(num); }

void print(unsigned long long num, int base) { printUint64(num, base); }

void print(double num) { printf("%f", num); }

void println(const char *s) { printf("%s\n", s); }
void println(char c) { printf("%c\n", c); }

void println(unsigned char num) { println(num, DEC); }

void println(unsigned char num, int base) {
  if (base == HEX)
    printf("%X\n", num);
  else
    printf("%d\n", num);
}

void println(int num) { println(num, DEC); }

void println(int num, int base) {
  if (base == HEX)
    printf("%X\n", num);
  else
    printf("%d\n", num);
}

void println(unsigned int num) { println(num, DEC); }

void println(unsigned int num, int base) {
  if (base == HEX)
    printf("%X\n", num);
  else
    printf("%d\n", num);
}

void println(long num) { println(num, DEC); }

void println(long num, int base) {
  if (base == HEX)
    printf("%lX\n", num);
  else
    printf("%ld\n", num);
}

void println(unsigned long num) { println(num, DEC); }

void println(unsigned long num, int base) {
  if (base == HEX)
    printf("%lX\n", num);
  else
    printf("%ld\n", num);
}

void println(unsigned long long num) {
  printUint64(num);
  println("");
}

void println(unsigned long long num, int base) {
  printUint64(num, base);
  println("");
}

void println(double num) { printf("%f\n", num); }

void println(double num, int places) { printf("%f\n", num); }

void println(void) { printf("\n"); }
#endif // KNX_NO_PRINT

void pinMode(uint32_t dwPin, uint32_t dwMode) {
  gpio_export(dwPin);
  gpio_direction(dwPin, dwMode);
}

void digitalWrite(uint32_t dwPin, uint32_t dwVal) { gpio_write(dwPin, dwVal); }

uint32_t digitalRead(uint32_t dwPin) { return gpio_read(dwPin); }

typedef void (*voidFuncPtr)(void);
void attachInterrupt(uint32_t pin, voidFuncPtr callback, uint32_t mode) {}

void LinuxPlatform::cmdLineArgs(int argc, char **argv) {
  if (_args)
    delete[] _args;
  _args = new char *[argc + 1];
  memcpy(_args, argv, argc * sizeof(char *));
  _args[argc] = 0;

  if (argc == 1) {
    printf("run [uartPath] [flashPath]\n");
    return;
  }
  if (argc == 2) {
    if (strcmp(_args[1], "-h") == 0) {
      printf("run [uartPath] [flashPath]\n");
      exit(0);
    }
  }
  if (argc == 3) {
    _flashFilePath = _args[1];
  }
}

/* Buffer size for string operations (e.g. snprintf())*/
#define MAX_STRBUF_SIZE 100
#define MAX_NUM_GPIO 64

static int gpioFds[MAX_NUM_GPIO] = {
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
};

/* Activate GPIO-Pin
 * Write GPIO pin number to /sys/class/gpio/export
 * Result: 0 = success, -1 = error
 */
int gpio_export(int pin) {
  char buffer[MAX_STRBUF_SIZE]; /* Output Buffer       */
  ssize_t bytes;                /* Used Buffer length  */
  int fd;                       /* Filedescriptor      */
  int res;                      /* Result from write() */

  fprintf(stderr, "Export GPIO pin %d\n", pin);

  fd = open("/sys/class/gpio/export", O_WRONLY);

  if (fd < 0) {
    perror("Could not export GPIO pin(open)!\n");
    return (-1);
  }

  bytes = snprintf(buffer, MAX_STRBUF_SIZE, "%d", pin);
  res = write(fd, buffer, bytes);

  if (res < 0) {
    perror("Could not export GPIO pin(write)!\n");
    return (-1);
  }

  close(fd);
  delay(100);

  return (0);
}

/* Deactivate GPIO pin
 * Write GPIO pin number to /sys/class/gpio/unexport
 * Result: 0 = success, -1 = error
 */
int gpio_unexport(int pin) {
  char buffer[MAX_STRBUF_SIZE]; /* Output Buffer       */
  ssize_t bytes;                /* Used Buffer length  */
  int fd;                       /* Filedescriptor      */
  int res;                      /* Result from write() */

  fprintf(stderr, "Unexport GPIO pin %d\n", pin);

  close(gpioFds[pin]);

  fd = open("/sys/class/gpio/unexport", O_WRONLY);

  if (fd < 0) {
    perror("Could not unexport GPIO pin(open)!\n");
    return (-1);
  }

  bytes = snprintf(buffer, MAX_STRBUF_SIZE, "%d", pin);
  res = write(fd, buffer, bytes);

  if (res < 0) {
    perror("Could not unexport GPIO pin(write)!\n");
    return (-1);
  }

  close(fd);
  return (0);
}

/* Set GPIO pin mode (input/output)
 * Write GPIO pin number to /sys/class/gpioXX/direction
 * Direction: 0 = input, 1 = output
 * Result: 0 = success, -1 = error
 */
int gpio_direction(int pin, int dir) {
  char path[MAX_STRBUF_SIZE]; /* Buffer for path     */
  int fd;                     /* Filedescriptor      */
  int res;                    /* Result from write() */

  fprintf(stderr, "Set GPIO direction for pin %d to %s\n", pin,
          (dir == INPUT) ? "INPUT" : "OUTPUT");

  snprintf(path, MAX_STRBUF_SIZE, "/sys/class/gpio/gpio%d/direction", pin);
  fd = open(path, O_WRONLY);

  if (fd < 0) {
    perror("Could not set mode for GPIO pin(open)!\n");
    return (-1);
  }

  switch (dir) {
  case INPUT:
    res = write(fd, "in", 2);
    break;

  case OUTPUT:
    res = write(fd, "out", 3);
    break;

  default:
    res = -1;
    break;
  }

  if (res < 0) {
    perror("Could not set mode for GPIO pin(write)!\n");
    return (-1);
  }

  close(fd);
  return (0);
}

/* Read from GPIO pin
 * Result: -1 = error, 0/1 = GPIO pin state
 */
int gpio_read(int pin) {
  char path[MAX_STRBUF_SIZE]; /* Buffer for path     */
  char c;

  snprintf(path, MAX_STRBUF_SIZE, "/sys/class/gpio/gpio%d/value", pin);

  if (gpioFds[pin] < 0)
    gpioFds[pin] = open(path, O_RDWR);

  if (gpioFds[pin] < 0) {
    perror("Could not read from GPIO(open)!\n");
    return (-1);
  }

  lseek(gpioFds[pin], 0L, SEEK_SET);

  if (read(gpioFds[pin], &c, 1) < 0) {
    perror("Could not read from GPIO(read)!\n");
    return (-1);
  }

  return (c == '0') ? LOW : HIGH;
}

/* Write to GPIO pin
 * Result: -1 = error, 0 = success
 */
int gpio_write(int pin, int value) {
  char path[MAX_STRBUF_SIZE]; /* Buffer for path    */
  int res;                    /* Result from write()*/

  snprintf(path, MAX_STRBUF_SIZE, "/sys/class/gpio/gpio%d/value", pin);

  if (gpioFds[pin] < 0)
    gpioFds[pin] = open(path, O_RDWR);

  if (gpioFds[pin] < 0) {
    perror("Could not write to GPIO(open)!\n");
    return (-1);
  }

  switch (value) {
  case LOW:
    res = write(gpioFds[pin], "0\n", 2);
    break;

  case HIGH:
    res = write(gpioFds[pin], "1\n", 2);
    break;

  default:
    res = -1;
    break;
  }

  if (res < 0) {
    perror("Could not write to GPIO(write)!\n");
    return (-1);
  }

  return (0);
}

/* Set GPIO pin edge detection
 * 'r' (rising)
 * 'f' (falling)
 * 'b' (both)
 */
int gpio_edge(unsigned int pin, char edge) {
  char path[MAX_STRBUF_SIZE]; /* Buffer for path    */
  int fd;                     /* Filedescriptor     */

  snprintf(path, MAX_STRBUF_SIZE, "/sys/class/gpio/gpio%d/edge", pin);

  fd = open(path, O_WRONLY | O_NONBLOCK);

  if (fd < 0) {
    perror("Could not set GPIO edge detection(open)!\n");
    return (-1);
  }

  switch (edge) {
  case 'r':
    strncpy(path, "rising", 8);
    break;

  case 'f':
    strncpy(path, "falling", 8);
    break;

  case 'b':
    strncpy(path, "both", 8);
    break;

  case 'n':
    strncpy(path, "none", 8);
    break;

  default:
    close(fd);
    return (-2);
  }

  write(fd, path, strlen(path) + 1);

  close(fd);
  return 0;
}

/* Wait for edge on GPIO pin
 * timeout in milliseconds
 * Result: <0: error, 0: poll() Timeout,
 * 1: edge detected, GPIO pin reads "0"
 * 2: edge detected, GPIO pin reads "1"
 */
int gpio_wait(unsigned int pin, int timeout) {
  char path[MAX_STRBUF_SIZE]; /* Buffer for path     */
  int fd;                     /* Filedescriptor      */
  struct pollfd polldat[1];   /* Variable for poll() */
  char buf[MAX_STRBUF_SIZE];  /* Read buffer         */
  int rc;                     /* Result              */

  /* Open GPIO pin */
  snprintf(path, MAX_STRBUF_SIZE, "/sys/class/gpio/gpio%d/value", pin);
  fd = open(path, O_RDONLY | O_NONBLOCK);

  if (fd < 0) {
    perror("Could not wait for GPIO edge(open)!\n");
    return (-1);
  }

  /* prepare poll() */
  memset((void *)buf, 0, sizeof(buf));
  memset((void *)polldat, 0, sizeof(polldat));
  polldat[0].fd = fd;
  polldat[0].events = POLLPRI;

  /* clear any existing detected edges before */
  lseek(fd, 0, SEEK_SET);
  rc = read(fd, buf, MAX_STRBUF_SIZE - 1);

  rc = poll(polldat, 1, timeout);

  if (rc < 0) {
    /* poll() failed! */
    perror("Could not wait for GPIO edge(poll)!\n");
    close(fd);
    return (-1);
  }

  if (rc == 0) {
    /* poll() timeout! */
    close(fd);
    return (0);
  }

  if (polldat[0].revents & POLLPRI) {
    if (rc < 0) {
      /* read() failed! */
      perror("Could not wait for GPIO edge(read)!\n");
      close(fd);
      return (-2);
    }

    /* printf("poll() GPIO %d interrupt occurred: %s\n", pin, buf); */
    close(fd);
    return (1 + atoi(buf));
  }

  close(fd);
  return (-1);
}

void delayMicrosecondsHard(unsigned int howLong) {
  struct timeval tNow, tLong, tEnd;

  gettimeofday(&tNow, NULL);
  tLong.tv_sec = howLong / 1000000;
  tLong.tv_usec = howLong % 1000000;
  timeradd(&tNow, &tLong, &tEnd);

  while (timercmp(&tNow, &tEnd, <))
    gettimeofday(&tNow, NULL);
}

void delayMicroseconds(unsigned int howLong) {
  struct timespec sleeper;
  unsigned int uSecs = howLong % 1000000;
  unsigned int wSecs = howLong / 1000000;

  /**/ if (howLong == 0)
    return;
  else if (howLong < 100)
    delayMicrosecondsHard(howLong);
  else {
    sleeper.tv_sec = wSecs;
    sleeper.tv_nsec = (long)(uSecs * 1000L);
    nanosleep(&sleeper, NULL);
  }
}

bool LinuxPlatform::sendBytesUniCast(uint32_t addr, uint16_t port,
                                     uint8_t *buffer, uint16_t len) {
  struct sockaddr_in address = {0};
  address.sin_family = AF_INET;
  address.sin_addr.s_addr = htonl(addr);
  address.sin_port = htons(port);

  ssize_t retVal = 0;

  do {
    retVal = sendto(_multicastSocketFd, buffer, len, 0,
                    (struct sockaddr *)&address, sizeof(address));

    if (retVal == -1) {
      if (errno != EAGAIN && errno != EWOULDBLOCK)
        return false;
    }
  } while (retVal == -1);

  printHex("<-", buffer, len);
  return true;
}

void LinuxPlatform::macAddress(uint8_t *mac_address) {
  memcpy(mac_address, _macAddress, IFHWADDRLEN);
}

uint32_t LinuxPlatform::currentIpAddress() { return _ipAddress; }

uint32_t LinuxPlatform::currentSubnetMask() { return _netmask; }

uint32_t LinuxPlatform::currentDefaultGateway() { return _defaultGateway; }
#endif
